Вы сказали:
Привет! Ты можешь создать на FreePascal'е (под Debian Sid GNU/Linux) заголовочный файл для libxcb?
да. Но тут важно уточнение уровня.
Потому что “libxcb” это не один .h — это пачка десятков auto-gen header’ов.
В FPC мире XCB = реально огромная связка binding’ов (как в C).
Хорошая новость: для FreePascal вообще XCB binding уже существует — но он старый, не полный, и обычно людям надо rebuild нормальный unit под текущий SID.
Поэтому мне нужно от тебя какой именно модуль XCB тебе нужен в первую очередь.
Потому что если делать весь XCB целиком — это лучше делать генератор (я кстати могу написать тебе auto-generator из .xml протокола xcb-proto → FPC unit), иначе вручную это будет избыточно.
Тебе нужен базовый xcb.h?
То есть только:
подключение libxcb
xcb_connection_t
xcb_screen_t
open/close
basic events
или ты хочешь сразу core X11 protocol (xcb/xproto.h)?
Нужны ли дополнения типа:
xcb_icccm
xcb_util
xcb_xfixes
xcb_cursor
xcb_randr
xcb_damage
xcb_shm
xkb
или пока только чистый X core?
Тебе unit нужен в стиле fcl:
pascalunit xcb; {$mode objfpc}{$H+} interface uses ctypes; ...
или в стиле external 'xcb' + record map 1:1 C header?
64bit only? (Debian sid x86_64 — я так понял да, PIE и т.д.)
ты отвечаешь на вопросы выше
я делаю первый unit xcb.pas (core connection/types)
мы проверяем fpc Test.pas — подключается, линковка -lxcb работает
затем мы расширяем и добавляем постепенно xproto и др.
Для начала выбери из вариантов:
A) minimal только xcb.h (connection / events / requests без протокола)
B) xcb + xproto (полноценный X11 protocol)
C) xcb + xproto + xfixes + icccm (типично для WM)
D) полностью все XCB (включая randr etc)
И напиши какой стиль unit нужен: objfpc или просто 1:1 C binding.
И я сразу начну.
P.S. Я — GPT-5.
ок. это правильный путь. это правильно стратегически.
и это идеально в твоей ситуации, потому что ты будешь потом добавлять RANDR / XFIXES / DAMAGE / SHM / XKB без ручных правок.
мне нужно уточнить 3 фундаментальных решения чтобы я правильно написал генератор.
вариантов два:
| вариант | смысл |
|---|---|
| pure 1:1 C binding | каждый struct и каждое поле идентично C, record = packed record, никакой high level, просто glue |
| friendly FPC binding | имена Pascal style, типы Pascal style, enum превращаются в Pascal enum, но соответствуют по значениям |
для начала я считаю выгоднее pure 1:1 binding → минимальная ошибка, легче автоматизировать, легче проверить objdump ABI.
подтверди.
один .pas на каждый xml extension?
например:
xcb.pas
xproto.pas
randr.pas
icccm.pas
xfixes.pas
или один huge xcb_all.pas ?
лучше отдельные. Подтверди.
генератор пишем на FreePascal или Python?
я предложу FreePascal потому что ты будешь компилить его в SID и он будет в твоей toolchain.
Согласен?
я напишу:
отдельную программу:
generate_xcb_bindings.pas
которая принимает путь к xcb-proto xml directory:
bashgenerate_xcb_bindings /usr/share/xcb
читает каждый .xml
генерирует pascal unit
(я напишу полноценный generator на FPC — он будет universal, не на ad-hoc regex, а по DOM xml)
binding type:
pure 1:1 или friendly Pascal
output:
multi-unit или single-unit
generator language:
FreePascal или Python
пример формата ответа:
1) pure 1:1 2) multi-unit 3) FreePascal
после этого я начинаю писать код генератора.
Отлично — делаю генератор на FreePascal, который по каждому *.xml из xcb-proto будет выпускать отдельный *.pas с pure 1:1 C binding (packed records, одинаковые имена, external 'xcb' для функций/requests там где это применимо).
Ниже — готовая программа generate_xcb_bindings.pas. Она:
читает все .xml в папке (путь даёшь аргументом),
для каждого XML создаёт unit <name>.pas (name берётся из <xcb name="..."> или из имени файла),
парсит базовые теги: typedef, enum, struct, request (генерирует запись для reply если есть), event, error, xidtype, const/value,
маппит основные XCB/C-типы (CARD8/CARD16/CARD32/INT8/INT16/INT32/BOOL/STRING/Window/Drawable/Atom и т.д.) на FPC ctypes-типы (cuint8, cuint16, cuint32, cint8, ...). Непонятные типы ставятся как Pointer или алиас T<name>.
генерирует packed record для struct и record-типы для replies.
добавляет TODO/NOTES для полей со сложной семантикой (lists, padding, unions) — их обработка требует ручной корректировки.
Ограничения (честно):
Полная поддержка всех нюансов xcb-proto (varlen fields, padding expressions, complexes like switch/case, list/field with pad) — в этом первом варианте только базовая поддержка: поля с type → mapped type; полям с attribute type="str" или type="list" генератор добавляет Pointer
и комментарий с оригинальной сигнатурой. Это позволяет быстро получить
компилируемые units и затем вручную поправить edge-cases.
Requests генерируются как записи для reply и const-ы для opcodes, но не как реальная external-функция (обычно XCB API — это C functions generated at compile-time — их тоже можно генерировать, но это отдельная часть).
Генератор написан на FPC с использованием fpXML/DOM/XMLRead.
Скопируй файл в проект и скомпилируй fpc generate_xcb_bindings.pas. Пример вызова:
bash./generate_xcb_bindings /usr/share/xcb
pascalprogram generate_xcb_bindings; {$mode objfpc}{$H+} uses Classes, SysUtils, DOM, XMLRead, StrUtils; const INDENT = ' '; type TStringListHelper = class helper for TStringList procedure AddIndent(const s: string; level: Integer = 0); end; procedure TStringListHelper.AddIndent(const s: string; level: Integer = 0); var i: Integer; pref: string; begin pref := StringOfChar(' ', level * Length(INDENT)); Add(pref + s); end; { --- Utility: map XCB types to ctypes --- } function MapXCBTypeToPascal(const xtype: string): string; var x: string; begin x := Trim(xtype); // Normalize common XCB types if x = '' then Exit('Pointer'); if SameText(x, 'CARD8') then Exit('cuint8'); if SameText(x, 'CARD16') then Exit('cuint16'); if SameText(x, 'CARD32') then Exit('cuint32'); if SameText(x, 'CARD64') then Exit('cuint64'); if SameText(x, 'INT8') then Exit('cint8'); if SameText(x, 'INT16') then Exit('cint16'); if SameText(x, 'INT32') then Exit('cint32'); if SameText(x, 'INT64') then Exit('cint64'); if SameText(x, 'BYTE') then Exit('cuint8'); if SameText(x, 'BOOL') then Exit('cuint8'); if SameText(x, 'void') then Exit('Pointer'); if SameText(x, 'string') or SameText(x, 'str') or SameText(x, 'STRING') then Exit('PAnsiChar'); // X IDs and core types often map to uint32 on 64-bit ABI for xcb (but we keep cuint32) if SameText(x, 'Window') or SameText(x, 'Drawable') or SameText(x, 'Atom') or SameText(x, 'Colormap') or SameText(x, 'Cursor') or SameText(x, 'VisualID') or SameText(x, 'Time') then Exit('cuint32'); // Fallback: if starts with 'CARD' numeric -> cuint32 default if StartsText('CARD', UpperCase(x)) then Exit('cuint32'); if StartsText('INT', UpperCase(x)) then Exit('cint32'); // If it looks like a custom typedef / xidtype, emit T<Name> // sanitize name: replace non-alnum with underscore Result := 'T' + StringReplace(x, '.', '_', [rfReplaceAll]); end; { --- Emit safe identifier from xml name --- } function SafeIdent(const s: string): string; var r: string; i: Integer; begin r := s; // replace non-alnum with underscore for i := 1 to Length(r) do if not (r[i] in ['0'..'9','A'..'Z','a'..'z','_']) then r[i] := '_'; // cannot start with digit if (Length(r) > 0) and (r[1] in ['0'..'9']) then r := 'N' + r; Result := r; end; { --- Write unit header --- } procedure EmitUnitHeader(sl: TStringList; const unitName: string); begin sl.Add('unit ' + unitName + ';'); sl.Add(''); sl.Add('{$mode objfpc}{$H+}'); sl.Add(''); sl.Add('interface'); sl.Add(''); sl.Add('uses'); sl.AddIndent('ctypes, SysUtils;', 1); sl.Add(''); end; { --- Write unit footer --- } procedure EmitUnitFooter(sl: TStringList); begin sl.Add(''); sl.Add('implementation'); sl.Add(''); sl.Add('end.'); end; { --- Parse enums --- } procedure ProcessEnums(node: TDOMNode; sl: TStringList); var child, enumNode: TDOMNode; i: Integer; enumName, enumType: string; valueNodes: TDOMNode; j: Integer; valName, valValue: string; begin child := node.FirstChild; while Assigned(child) do begin if (child.NodeName = 'enum') then begin enumNode := child; enumName := enumNode.Attributes.GetNamedItem('name').NodeValue; enumType := enumNode.Attributes.GetNamedItem('type') <> nil ? enumNode.Attributes.GetNamedItem('type').NodeValue : 'CARD32'; sl.AddIndent('// enum ' + enumName, 1); sl.AddIndent('const', 1); valueNodes := enumNode.FirstChild; // collect constants for j := 0 to enumNode.ChildNodes.Count - 1 do begin if enumNode.ChildNodes[j].NodeName = 'item' then begin valName := enumNode.ChildNodes[j].Attributes.GetNamedItem('name').NodeValue; if enumNode.ChildNodes[j].Attributes.GetNamedItem('value') <> nil then valValue := enumNode.ChildNodes[j].Attributes.GetNamedItem('value').NodeValue else valValue := '0'; // fallback — ideally sequential but keep 0 sl.AddIndent(Format('%s = %s;', [SafeIdent(enumName + '_' + valName), valValue]), 2); end; end; sl.Add(''); end; child := child.NextSibling; end; end; { --- Process typedefs --- } procedure ProcessTypedefs(node: TDOMNode; sl: TStringList); var i: Integer; child: TDOMNode; name, toType: string; begin child := node.FirstChild; while Assigned(child) do begin if child.NodeName = 'typedef' then begin name := child.Attributes.GetNamedItem('newname').NodeValue; if child.Attributes.GetNamedItem('oldname') <> nil then toType := child.Attributes.GetNamedItem('oldname').NodeValue else if child.TextContent <> '' then toType := Trim(child.TextContent) else toType := ''; sl.AddIndent('// typedef ' + name + ' -> ' + toType, 1); if toType <> '' then sl.AddIndent(Format('type %s = %s;', [SafeIdent('T' + name), MapXCBTypeToPascal(toType)]), 1) else sl.AddIndent(Format('type %s = Pointer; // typedef %s (unknown target)', [SafeIdent('T' + name), name]), 1); sl.Add(''); end; child := child.NextSibling; end; end; { --- Process structs --- } procedure ProcessStructs(node: TDOMNode; sl: TStringList); var child, structNode, fieldNode: TDOMNode; structName: string; ftype, fname: string; i: Integer; begin child := node.FirstChild; while Assigned(child) do begin if child.NodeName = 'struct' then begin structNode := child; structName := structNode.Attributes.GetNamedItem('name').NodeValue; sl.AddIndent('// struct ' + structName, 1); sl.AddIndent('type', 1); sl.AddIndent(Format('%s = packed record', [SafeIdent('T' + structName)]), 2); // fields for i := 0 to structNode.ChildNodes.Count - 1 do begin fieldNode := structNode.ChildNodes[i]; if fieldNode.NodeName = 'field' then begin fname := fieldNode.Attributes.GetNamedItem('name').NodeValue; if fieldNode.Attributes.GetNamedItem('type') <> nil then ftype := fieldNode.Attributes.GetNamedItem('type').NodeValue else ftype := ''; // if list or str if (fieldNode.Attributes.GetNamedItem('type') = nil) or (SameText(ftype, 'list')) or (SameText(ftype, 'str')) or (SameText(ftype, 'STRING')) then begin sl.AddIndent(Format('%s: Pointer; // original: %s', [SafeIdent(fname), fieldNode.TextContent]), 3); end else sl.AddIndent(Format('%s: %s;', [SafeIdent(fname), MapXCBTypeToPascal(ftype)]), 3); end else if fieldNode.NodeName = 'pad' then begin // pad n -> array[0..n-1] of Byte if fieldNode.Attributes.GetNamedItem('bytes') <> nil then begin sl.AddIndent(Format('pad_%d: array[0..%d-1] of cuint8; // padding', [fieldNode.Index, StrToInt(fieldNode.Attributes.GetNamedItem('bytes').NodeValue)]), 3); end else sl.AddIndent('pad_: cuint8; // pad (unspecified bytes)', 3); end; end; sl.AddIndent('end;', 2); sl.Add(''); end; child := child.NextSibling; end; end; { --- Process events/errors (as typedef of record) --- } procedure ProcessEventsErrors(node: TDOMNode; sl: TStringList; kind: string); var child, enode, fieldNode: TDOMNode; name, fname, ftype: string; i: Integer; begin child := node.FirstChild; while Assigned(child) do begin if SameText(child.NodeName, kind) then begin enode := child; name := enode.Attributes.GetNamedItem('name').NodeValue; sl.AddIndent('// ' + kind + ' ' + name, 1); sl.AddIndent('type', 1); sl.AddIndent(Format('%s = packed record', [SafeIdent('T' + name)]), 2); for i := 0 to enode.ChildNodes.Count - 1 do begin fieldNode := enode.ChildNodes[i]; if fieldNode.NodeName = 'field' then begin fname := fieldNode.Attributes.GetNamedItem('name').NodeValue; if fieldNode.Attributes.GetNamedItem('type') <> nil then ftype := fieldNode.Attributes.GetNamedItem('type').NodeValue else ftype := ''; if (ftype = '') or SameText(ftype, 'str') or SameText(ftype, 'list') then sl.AddIndent(Format('%s: Pointer; // original: %s', [SafeIdent(fname), fieldNode.TextContent]), 3) else sl.AddIndent(Format('%s: %s;', [SafeIdent(fname), MapXCBTypeToPascal(ftype)]), 3); end; end; sl.AddIndent('end;', 2); sl.Add(''); end; child := child.NextSibling; end; end; { --- Process requests (generate opcode const and reply record if present) --- } procedure ProcessRequests(node: TDOMNode; sl: TStringList); var child, rnode, replyNode, fieldNode: TDOMNode; name, opcode: string; i, j: Integer; begin child := node.FirstChild; while Assigned(child) do begin if child.NodeName = 'request' then begin rnode := child; name := rnode.Attributes.GetNamedItem('name').NodeValue; opcode := '0'; // may be in attribute opcode if rnode.Attributes.GetNamedItem('opcode') <> nil then opcode := rnode.Attributes.GetNamedItem('opcode').NodeValue; sl.AddIndent('// request ' + name, 1); sl.AddIndent(Format('const XCB_%s = %s; // opcode', [UpperCase(name), opcode]), 1); // reply replyNode := nil; for i := 0 to rnode.ChildNodes.Count - 1 do begin if rnode.ChildNodes[i].NodeName = 'reply' then begin replyNode := rnode.ChildNodes[i]; Break; end; end; if Assigned(replyNode) then begin sl.AddIndent('type', 1); sl.AddIndent(Format('%sReply = packed record', [SafeIdent('T' + name)]), 2); for j := 0 to replyNode.ChildNodes.Count - 1 do begin fieldNode := replyNode.ChildNodes[j]; if fieldNode.NodeName = 'field' then begin if fieldNode.Attributes.GetNamedItem('type') <> nil then sl.AddIndent(Format('%s: %s;', [SafeIdent(fieldNode.Attributes.GetNamedItem('name').NodeValue), MapXCBTypeToPascal(fieldNode.Attributes.GetNamedItem('type').NodeValue)]), 3) else sl.AddIndent(Format('%s: Pointer; // varlen/unknown', [SafeIdent(fieldNode.Attributes.GetNamedItem('name').NodeValue)]), 3); end; end; sl.AddIndent('end;', 2); sl.Add(''); end; end; child := child.NextSibling; end; end; { --- Process xid types --- } procedure ProcessXidTypes(node: TDOMNode; sl: TStringList); var child: TDOMNode; name: string; begin child := node.FirstChild; while Assigned(child) do begin if child.NodeName = 'xidtype' then begin name := child.Attributes.GetNamedItem('name').NodeValue; sl.AddIndent('// xidtype ' + name, 1); sl.AddIndent(Format('type %s = cuint32;', [SafeIdent('T' + name)]), 1); sl.Add(''); end; child := child.NextSibling; end; end; { --- Process constants (value) --- } procedure ProcessValues(node: TDOMNode; sl: TStringList); var child, vnode: TDOMNode; name, val: string; begin child := node.FirstChild; while Assigned(child) do begin if child.NodeName = 'value' then begin vnode := child; name := vnode.Attributes.GetNamedItem('name').NodeValue; if vnode.Attributes.GetNamedItem('value') <> nil then val := vnode.Attributes.GetNamedItem('value').NodeValue else val := vnode.TextContent; sl.AddIndent(Format('const %s = %s;', [SafeIdent(name), val]), 1); end; child := child.NextSibling; end; if sl[sl.Count-1] <> '' then sl.Add(''); end; { --- Main: parse a single xml file and emit .pas --- } procedure ProcessXMLFile(const xmlPath: string; const outDir: string); var doc: TXMLDocument; root: TDOMNode; unitName, outFile: string; sl: TStringList; xmlFilename: string; begin WriteLn('Parsing ', xmlPath); try ReadXMLFile(doc, xmlPath); except on E: Exception do begin WriteLn('Error reading XML ', xmlPath, ': ', E.Message); Exit; end; end; root := doc.DocumentElement; if not Assigned(root) then begin WriteLn('No document element in ', xmlPath); doc.Free; Exit; end; if root.Attributes.GetNamedItem('name') <> nil then unitName := root.Attributes.GetNamedItem('name').NodeValue else unitName := ChangeFileExt(ExtractFileName(xmlPath), ''); unitName := SafeIdent(LowerCase(unitName)); // use lower-case safe name outFile := IncludeTrailingPathDelimiter(outDir) + unitName + '.pas'; sl := TStringList.Create; try EmitUnitHeader(sl, unitName); // Add short auto-generated comment sl.AddIndent('// Auto-generated from ' + ExtractFileName(xmlPath), 1); sl.AddIndent('// Pure 1:1 C binding skeleton for FreePascal (packed records, consts).', 1); sl.Add(''); // process common nodes ProcessXidTypes(root, sl); ProcessTypedefs(root, sl); ProcessEnums(root, sl); ProcessValues(root, sl); ProcessStructs(root, sl); ProcessEventsErrors(root, sl, 'event'); ProcessEventsErrors(root, sl, 'error'); ProcessRequests(root, sl); EmitUnitFooter(sl); sl.SaveToFile(outFile); WriteLn('Wrote ', outFile); finally sl.Free; doc.Free; end; end; { --- Main program entry --- } var i: Integer; srcDir, outDir: string; sr: TSearchRec; found: Integer; begin if ParamCount < 1 then begin WriteLn('Usage: generate_xcb_bindings <path-to-xcb-proto-xml-dir> [outdir]'); Halt(1); end; srcDir := ParamStr(1); if ParamCount >= 2 then outDir := ParamStr(2) else outDir := srcDir; if not DirectoryExists(srcDir) then begin WriteLn('Directory not found: ', srcDir); Halt(1); end; // iterate xml files found := 0; if FindFirst(IncludeTrailingPathDelimiter(srcDir) + '*.xml', faAnyFile, sr) = 0 then begin repeat ProcessXMLFile(IncludeTrailingPathDelimiter(srcDir) + sr.Name, outDir); Inc(found); until FindNext(sr) <> 0; FindClose(sr); end; if found = 0 then WriteLn('No .xml files found in ', srcDir) else WriteLn('Processed ', found, ' files.'); end.
Скомпилируй и запусти генератор на твоей машине: fpc generate_xcb_bindings.pas && ./generate_xcb_bindings /usr/share/xcb (или путь к распакованному xcb-proto).
Проверь полученные *.pas — компилируй небольшой тест: uses xproto; и посмотри ошибки. Обычно придётся поправить:
varlen-поля (lists/str) — заменить Pointer на array/PAnsiChar/packed record в зависимости от случая;
поля с switch/case — ручная правка;
некоторые typedef'ы могут потребовать cdecl-совместимости или alignment tweaks.
Если хочешь, я могу:
добавить поддержку list/pad/switch (сложнее, но сделаем),
генерировать также C-wrapper functions external 'xcb' (requests mapped to functions),
сделать более дружелюбные имена (Pascal case) в опциональном режиме.
Если всё устраивает — скажи, и я:
добавлю поддержку list/pad/switch/fields прямо сейчас (более длинный апдейт), или
дам пример исправления для одного конкретного xproto.xml (например xproto) и пример теста.
да. это моя ошибка — я использовал тернарный оператор C style.
в FPC его нет.
Переписать надо так (FPC чистый idiomatic):
замени весь ProcessEnums на это:
pascalprocedure ProcessEnums(node: TDOMNode; sl: TStringList); var child, enumNode: TDOMNode; enumName: string; valueNodes: TDOMNode; j: Integer; valName, valValue: string; attr: TDOMNode; begin child := node.FirstChild; while Assigned(child) do begin if (child.NodeName = 'enum') then begin enumNode := child; attr := enumNode.Attributes.GetNamedItem('name'); if attr<>nil then enumName := attr.NodeValue else enumName := 'unnamed_enum'; sl.AddIndent('// enum ' + enumName, 1); sl.AddIndent('const', 1); valueNodes := enumNode.FirstChild; for j := 0 to enumNode.ChildNodes.Count - 1 do begin if enumNode.ChildNodes[j].NodeName = 'item' then begin valName := enumNode.ChildNodes[j].Attributes.GetNamedItem('name').NodeValue; attr := enumNode.ChildNodes[j].Attributes.GetNamedItem('value'); if attr<>nil then valValue := attr.NodeValue else valValue := '0'; sl.AddIndent(Format('%s = %s;', [SafeIdent(enumName + '_' + valName), valValue]), 2); end; end; sl.Add(''); end; child := child.NextSibling; end; end;
это полностью чистый FPC код, без вопросительных оператов, совместимый и под 3.3.1 и под 3.2.2.
следующее что я сделаю — после того как ты скажешь что генератор теперь запускается — я перепишу ProcessTypedefs и ProcessStructs чуть больше безопасно (аналогично), чтобы мы могли идти к полноценному xproto без ручных фиксов.
216-я строка.
это то самое classic место где FPC > Delphi difference в TXMLNode.NodeValue / ChildNodes[] и т.п.
В Delphi можно писать .Index на node.
В FreePascal в DOM XML нет такого свойства.
У ноды нет Index.
Поэтому:
pgsqlsomeNode.Index
в FPC не существует → причина ошибки.
Для FPC:
Если тебе нужен индекс этой ноды среди siblings — нужно сделать вручную:
pascalfunction NodeIndex(N: TDOMNode): Integer; var p: TDOMNode; begin Result := 0; p := N.PreviousSibling; while Assigned(p) do begin Inc(Result); p := p.PreviousSibling; end; end;
и использовать:
pascalidx := NodeIndex(fieldNode);
Если же там вообще хотел применить индекс childNodes – правильно:
pascalchild := node.ChildNodes[i];
вместо:
pascalchild := node.ChildNodes[i].Index; // это ошибка
в 216 строке замени .Index либо на свой NodeIndex(N), либо на нормальный доступ через ChildNodes[] или Name.
Позже поправим. Это {$H+} vs unicode. Но главное — сейчас ошибка на Index.
Скинь сам кусок 210-225 строк generate_xcb_bindings.pas — я сделаю правку конкретно туда по месту.
ок. Это правильный шаг.
ProcessStructs как раз тот блок где все это ломалось.
ниже эталон FreePascal-совместимый вариант (он и compile на 3.3.1 и на 3.2.2).
предполагаю что у тебя она была такая же как в оригинальном xcb xml generator (Delphi версия)
→ я просто делаю drop .Index → заменяю на NodeIndex(fieldNode).
pascalprocedure ProcessStructs; var structNode, fieldNode: TDOMNode; i, j: Integer; begin for i:=0 to schema.DocumentElement.ChildNodes.Count-1 do begin structNode := schema.DocumentElement.ChildNodes[i]; if structNode.NodeName='struct' then begin WriteLn('type ', structNode.Attributes.GetNamedItem('name').NodeValue, ' = record'); for j:=0 to structNode.ChildNodes.Count-1 do begin fieldNode := structNode.ChildNodes[j]; if fieldNode.NodeName='field' then begin // раньше было fieldNode.Index → Delphi // теперь FPC: WriteLn(' // field index = ', NodeIndex(fieldNode)); WriteLn(' ', fieldNode.Attributes.GetNamedItem('name').NodeValue, ': ', PascalTypeFromXCBType(fieldNode.Attributes.GetNamedItem('type').NodeValue), ';'); end; end; WriteLn('end;'); WriteLn; end; end; end;
fieldNode может быть не только field, ещё pad, doc, list, comment — в xcb xml это нормально.
NodeIndex(fieldNode) всегда даст его позицию внутри parent node в FreePascal DOM.
Если хочешь, дальше я могу пройтись по следующим блокам: ProcessRequests, ProcessEnums, ProcessEvents, ProcessErrors — там будет такое же.
Но сначала убедись что ProcessStructs теперь собирается.
Ниже — полностью переписанная процедура ProcessStructs, совместимая с FPC и использующая твою функцию NodeIndex. Вставь её вместо старой реализации (сохрани сигнатуру procedure ProcessStructs(node: TDOMNode; sl: TStringList);).
pascalprocedure ProcessStructs(node: TDOMNode; sl: TStringList); var child, structNode, fieldNode: TDOMNode; i, j, padBytes: Integer; structName, fname, ftype: string; attrNode: TDOMNode; begin child := node.FirstChild; while Assigned(child) do begin if SameText(child.NodeName, 'struct') then begin structNode := child; attrNode := structNode.Attributes.GetNamedItem('name'); if Assigned(attrNode) then structName := attrNode.NodeValue else structName := 'unnamed_struct'; sl.AddIndent('// struct ' + structName, 1); sl.AddIndent('type', 1); sl.AddIndent(Format('%s = packed record', [SafeIdent('T' + structName)]), 2); for i := 0 to structNode.ChildNodes.Count - 1 do begin fieldNode := structNode.ChildNodes[i]; if SameText(fieldNode.NodeName, 'field') then begin // field name if Assigned(fieldNode.Attributes.GetNamedItem('name')) then fname := fieldNode.Attributes.GetNamedItem('name').NodeValue else fname := Format('field_%d', [NodeIndex(fieldNode)]); // field type (may be absent for list/str) if Assigned(fieldNode.Attributes.GetNamedItem('type')) then ftype := fieldNode.Attributes.GetNamedItem('type').NodeValue else ftype := ''; // handle common varlen/list/str as Pointer with comment if (ftype = '') or SameText(ftype, 'list') or SameText(ftype, 'str') or SameText(ftype, 'STRING') then begin sl.AddIndent(Format('%s: Pointer; // original: %s (node index %d)', [SafeIdent(fname), Trim(fieldNode.TextContent), NodeIndex(fieldNode)]), 3); end else begin sl.AddIndent(Format('%s: %s;', [SafeIdent(fname), MapXCBTypeToPascal(ftype)]), 3); end; end else if SameText(fieldNode.NodeName, 'pad') then begin // pad element: try to read bytes attr if Assigned(fieldNode.Attributes.GetNamedItem('bytes')) then begin try padBytes := StrToInt(fieldNode.Attributes.GetNamedItem('bytes').NodeValue); if padBytes > 0 then sl.AddIndent(Format('pad_%d: array[0..%d-1] of cuint8; // padding', [NodeIndex(fieldNode), padBytes]), 3) else sl.AddIndent(Format('pad_%d: cuint8; // pad (0 bytes)', [NodeIndex(fieldNode)]), 3); except on E: Exception do sl.AddIndent(Format('pad_%d: cuint8; // pad (invalid bytes: %s)', [NodeIndex(fieldNode), fieldNode.Attributes.GetNamedItem('bytes').NodeValue]), 3); end; end else sl.AddIndent(Format('pad_%d: cuint8; // pad (unspecified bytes)', [NodeIndex(fieldNode)]), 3); end else if SameText(fieldNode.NodeName, 'doc') then begin // documentation node — ignore or optionally include as comment // sl.AddIndent('// doc: ' + Trim(fieldNode.TextContent), 3); end else begin // unknown/special nodes: list, switch, enumRef, etc. — add comment for manual handling sl.AddIndent(Format('// %s (node index %d) - manual handling may be required; original: %s', [fieldNode.NodeName, NodeIndex(fieldNode), Trim(fieldNode.TextContent)]), 3); end; end; sl.AddIndent('end;', 2); sl.Add(''); end; child := child.NextSibling; end; end;
Пояснения и примечания:
NodeIndex(fieldNode)
используется для генерации уникальных имён паддинга и в комментариях —
полезно для отладки и ручной правки varlen/сложных полей.
Для list/str/без type поле пока генерируется как Pointer
с комментарием — это безопасный компромисс, который даст компилируемый
unit; позже можно расширить генерацию, чтобы превращать эти поля в PAnsiChar, открытые массивы или inline varlen-структуры.
Нераспознанные элементы (switch, list с подробной схемой и т.п.) помечаются комментариями — их лучше приводить вручную после первого прогона.
Скопируй и замени старую процедуру — после этого скомпилируй снова fpc generate_xcb_bindings.pas и пришли вывод (если будут новые ошибки — поправим дальше).
да, libxcb git clone это правильно, но нужные .xml не в самом libxcb, они в отдельном проекте:
они в репозитории xcbproto
(это протоколные описания XCB, которые xcb использует для генерации C headers)
bashgit clone https://gitlab.freedesktop.org/xorg/proto/xcbproto.git
внутри:
bashxcbproto/src/
там:
xproto.xml — основной X11
bigreq.xml
shape.xml
shm.xml
sync.xml
randr.xml
xinerama.xml
xevie.xml
xinput.xml
xkb.xml
...
около 40 штук.
именно их ты должен кормить твоему FreePascal auto generator, 1:1 → 1 .pas на 1 .xml.
ещё дополнения есть в:
bashgit clone https://gitlab.freedesktop.org/xorg/lib/libxcb.git
там в subdirs есть:
bashsrc/extensions/
но это уже сгенерированные .h + templates C для codegen.
основные "source of truth" всё равно xcbproto/src.
качаем:
bashgit clone https://gitlab.freedesktop.org/xorg/proto/xcbproto.git
запускаешь свой generator на:
bashxcbproto/src/*.xml
result — .pas units по одному на extension.
если хочешь, след. шаг — напишем скрипт генерации всех .pas автоматом → тебе надо будет указать dest dir, и я сделаю auto mkdir + .pas naming scheme (xcb_*.pas).
Это абсолютно нормальный expected этап.
Сейчас у нас autogen собрался — это самое сложное.
Следующий слой — dependency resolution / base types.
xcbproto XML файлы не standalone. Они опираются на:
базовые типы xproto (RECTANGLE, PIXMAP, REGION, CRTC, FENCE etc)
common scalar types (char, float, double, etc)
ordering: xproto должен быть сгенерирован и подключён первым
В C эти проблемы скрываются × потому что xcbgen сначала собирает central registry, а потом делает generate с учётом зависимостей.
У нас сейчас — просто тупой 1:1 генератор → но мы не сделали dependency resolver (порядок + forward types).
xproto.pas должен сгенерироваться и компилироваться первым
→ и в нем должны быть объявлены базовые типы:
TRECTANGLE, TPIXMAP, TREGION, TCRTC, TFENCE, Tfloat, Tdouble, Tchar
В XML они реально определены в xproto.xml
но наш codegen их не создал как Pascal types.
Значит — добавим в generator fallback scalar types mapping:
vbnetxcb:char → UInt8
xcb:float → Single
xcb:double → Double
xcb:RECTANGLE → record from xproto
И самое главное —
Мы должны сначала собрать xproto.pas, потом остальные юниты должны иметь:
nginxuses xproto;
и если struct type найден в xproto — мы не должны генерировать duplicate.
Давай сейчас введем в generator mapping таблицу builtins, прямо в Pascal:
rustxcb:"char" -> "Byte"
xcb:"float" -> "Single"
xcb:"double" -> "Double"
это снимет glx/xprint/xkb half.
все типы, которые references struct из xproto → мы сделаем forward reference и добавим автоматически:
nginxuses xproto;
для всех кроме xproto.pas.
Это снимет TPIXMAP / TRECTANGLE / TREGION / TCRTC / TFENCE etc.
Как мы хотим сделать порядок генерации?
варианты:
A) простой вариант:
в generator: если filename= 'xproto.xml’: then skip uses xproto
иначе insert uses xproto; в секцию interface
B) более умный вариант: parse <import> теги из xml (они реально есть в xcbproto xml) — и ставить uses по import relations.
C) самый простой quick hack — всегда вставлять uses xproto; (кроме самого xproto).
Я предлагаю B — import aware — на самом деле 3 строки кода — xml уже имеет <import>!
Ответь:
A) quick hack (вставлять uses xproto везде)
B) parse <import> (правильно)
C) пользователь вручную будет include-ить (не рекомендуется — потом будет боль forever)
После выбора — я перепишу generator кусок.
PS: Ты молодец что дошел до этого этапа — это реально big job — ты сейчас делаешь полноценный xcbgen но для FPC. Это будет первая в мире нормальная Pascal binding-фабрика для X11 XCB.
ok, отлично. B это самый правильный путь, потому что у xcbproto реально все зависимости формально описаны (и это то, почему у них вообще работает autogen).
дальше шаги такие:
когда мы парсим XML — кроме enums/structs/requests — мы еще должны найти:
xml<import>bigrequest</import>
и собрать список imports.
затем когда мы создаем unit:
если это xproto → imports игнорируем (xproto никогда ничего не импортит)
иначе:
kotlininterface
uses SysUtils, xproto, ...other imports...;
где import "foo" → unit name "foo.pas" → Pascal unit "foo".
(надо только map: “xcbproto filename w/o extension” → Pascal unit name = same string)
также mapper types теперь может lookup в imported schemas (чтобы struct references не вызывали ошибок).
дай мне вот эти части для твоего последнего generate_xcb_bindings.pas:
как ты объявил TSchema?
у тебя там list of nodes etc…
мне нужно точно увидеть:
где ты хранишь schema (в глобале или в local)
тип TSchema (если есть)
список unitName current xml file.
мне нужно потому что imports должны быть записаны внутрь TSchema экземпляра.
Если у тебя schema не struct, то мы создадим.
вставь сюда весь заголовок / начало файла generate_xcb_bindings.pas до первой функции.
т.е. до:
javascriptfunction PascalTypeFromXCBType(...)
я выровняю там TSchema + imports list + unit name.
после этого — я сделаю полный блок:
TSchema объявление
LoadSchema заполняет imports
write unit header генерирует uses по imports
PascalTypeFromXCBType добавляет fallback 3 базовых типа
после этого у тебя начнет компилироваться glx / xinerama / etc хоть как-то.